#include "Sky.h"

CSky::CSky()
{
	pVertexBuffer = NULL;
	pIndexBuffer  = NULL;
	pSkyEffect    = NULL;
	pSunVertexBuffer  = NULL;

	pSunTexture  = NULL;

	sunAngle      = D3DX_PI/2;
	sunTrackAngle = D3DX_PI/2;
}

CSky::~CSky()
{

}

bool CSky::init()
{
	HRESULT hr;

	//Seed the random number generator
	srand(timeGetTime());

	//Get the Direct3D device
	pD3DDevice = DXUTGetD3DDevice();
	if(!pD3DDevice)
		return false;

	//Load sky gradient
	V(D3DXCreateTextureFromFile(pD3DDevice, L"../Data/clearSky.png", &pSkyGradient));

	//Create the sphere
	makeSphere(SKY_SMOOTHNESS, SKY_SMOOTHNESS);

	//Load the sky render effect
	DWORD dwShaderFlags = D3DXFX_NOT_CLONEABLE|D3DXSHADER_DEBUG;
    WCHAR str[MAX_PATH];
    V(DXUTFindDXSDKMediaFileCch( str, MAX_PATH, L"../Data/SkyDome.fx" ) );

    V(D3DXCreateEffectFromFile(pD3DDevice, str, NULL, NULL, dwShaderFlags, NULL, &pSkyEffect, NULL ) );

	//Generate sun
	generateSun();

	return true;
}

bool CSky::render(const D3DXVECTOR3* cameraPos)
{
	HRESULT hr;

	//Calculate the appropriate matrices
	D3DXMATRIX matTrans, matScale, matWorld, matProj, matView, matWorldViewProjection;
	D3DXMatrixTranslation(&matTrans, cameraPos->x, cameraPos->y-300, cameraPos->z);
	D3DXMatrixScaling(&matScale, SKY_RADIUS, SKY_RADIUS, SKY_RADIUS);
	D3DXMatrixMultiply(&matWorld, &matScale, &matTrans);

	//Get the projection & view matrices
	pD3DDevice->GetTransform(D3DTS_PROJECTION, &matProj);
	pD3DDevice->GetTransform(D3DTS_VIEW, &matView);
	pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld);
    matWorldViewProjection = matWorld * matView * matProj;

	//Disable culling
	pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

	//Set texture info
	V(pD3DDevice->SetTexture(0, pSkyGradient));
	pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
    pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_DISABLE);

	//Set vertex/index sources
	pD3DDevice->SetStreamSource(0, pVertexBuffer, 0, sizeof(SSkyVertex));
	pD3DDevice->SetIndices(pIndexBuffer);
	//V(pD3DDevice->SetFVF(D3DFVF_SKYVERTEX));

	V(pSkyEffect->SetTexture("gradientTexture", pSkyGradient));
	pSkyEffect->SetMatrix("matViewProjection", &matWorldViewProjection);

	//Update time
	static DWORD startTime = timeGetTime();
	DWORD elapsedTime = (timeGetTime() - startTime) / 1000;
	float timeOfDay = elapsedTime / 60.0f;

	V(pSkyEffect->SetFloat("timeOfDay", timeOfDay));

	D3DXHANDLE hTech;
	V(pSkyEffect->FindNextValidTechnique(NULL, &hTech));

	if (SUCCEEDED(pSkyEffect->SetTechnique(hTech)))
	{
		UINT numPasses;
		V(pSkyEffect->Begin(&numPasses,0));
	
		for (UINT i=0;i<numPasses;i++)
		{
			V(pSkyEffect->BeginPass(i)); // Set the pass

			//I'm lazy and don't feel like redoing make sphere to do a dome so just render the top half
			V(pD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP, 0, 0, (SKY_SMOOTHNESS+1)*(SKY_SMOOTHNESS+1), 0, SKY_SMOOTHNESS*(SKY_SMOOTHNESS+1)-2));

			V(pSkyEffect->EndPass());
		}
		V(pSkyEffect->End());
	}

	D3DXMatrixIdentity(&matWorld);
	V(pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld));

	//Render sun
	/*D3DXMatrixIdentity(&matWorld);

	//Rotate the billboard to face the camera
	D3DXMatrixTranspose(&matWorld, &matView);
	matWorld._41 = 20.0f;   //X
	matWorld._42 = 20.0f;   //Y
	matWorld._43 = 20.0f;   //Z

	V(pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld));

	//Setup the texturing
	V(pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1));
    V(pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE));
    V(pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG1));
	V(pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE));
	V(pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR));
    V(pD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR));
    V(pD3DDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT)); 

	V(pD3DDevice->SetStreamSource( 0, pSunVertexBuffer, 0, sizeof(SSkyVertex)));
	V(pD3DDevice->SetFVF( D3DFVF_SKYVERTEX ));
	V(pD3DDevice->SetTexture(0, pSunTexture));
	V(pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2 ));*/
	

	return true;
}

void CSky::cleanUp()
{
	if(pSunTexture)
	{
		pSunTexture->Release();
		pSunTexture = 0;
	}

	if(pSunVertexBuffer)
	{
		pSunVertexBuffer->Release();
		pSunVertexBuffer = 0;
	}

	if(pSkyGradient)
	{
		pSkyGradient->Release();
		pSkyGradient = 0;
	}

	if(pSkyEffect)
	{
		pSkyEffect->Release();
		pSkyEffect = 0;
	}

	if(pIndexBuffer)
	{
		pIndexBuffer->Release();
		pIndexBuffer = 0;
	}
	if(pVertexBuffer)
	{
		pVertexBuffer->Release();
		pVertexBuffer = 0;
	}
}

//Happy create sphere function
//Taken from http://www.gamedev.net/community/forums/topic.asp?topic_id=85779
bool CSky::makeSphere(int nRings , int nSegments) 
{
	//Set vertex count and index count 
	DWORD dwVertices = ( nRings + 1 ) * ( nSegments + 1 ) ;
	DWORD dwIndices = 2 * nRings * ( nSegments + 1 ) ;

	//Create the vertex buffer
	if(FAILED(pD3DDevice->CreateVertexBuffer(dwVertices * sizeof(SSkyVertex), 
		                                     D3DUSAGE_WRITEONLY, D3DFVF_SKYVERTEX, 
											 D3DPOOL_DEFAULT, &pVertexBuffer, NULL)))
		return false;

	//Create the index buffer
	if(FAILED(pD3DDevice->CreateIndexBuffer(dwIndices * 2 , D3DUSAGE_WRITEONLY, D3DFMT_INDEX16,
											D3DPOOL_DEFAULT, &pIndexBuffer, NULL)))
		return false;

	// Lock the vertex buffer
	SSkyVertex* pVertex; 
	pVertexBuffer->Lock(0, 0, (void**)&pVertex , 0);

	//Lock the index buffer 
	WORD * pIndices;
	if(FAILED(pIndexBuffer->Lock(0, dwIndices, (void**)&pIndices, 0)))
		return false;

	//Establish constants used in sphere generation
	FLOAT fDeltaRingAngle = ( D3DX_PI / nRings );
	FLOAT fDeltaSegAngle = ( 2.0f * D3DX_PI / nSegments );

	WORD wVerticeIndex = 0 ; 

	//Generate the group of rings for the sphere
	for(int ring = 0; ring < nRings + 1 ; ring++ )
	{
		FLOAT r0 = sinf ( ring * fDeltaRingAngle );
		FLOAT y0 = cosf ( ring * fDeltaRingAngle );

		//Generate the group of segments for the current ring
		for(int seg = 0; seg < nSegments + 1 ; seg++ )
		{
			FLOAT x0 = r0 * sinf( seg * fDeltaSegAngle );
			FLOAT z0 = r0 * cosf( seg * fDeltaSegAngle );

			//Add one vertices to the strip which makes up the sphere
			pVertex->x     = x0;
			pVertex->y     = y0;
			pVertex->z     = z0;

			//Normals
			//pVertex->nx = x0;
			//pVertex->ny = y0;
			//pVertex->nz = z0;

			//Calculate the color
			//pVertex->color = calculateColor(x0, y0, z0);
			
			//Textures not currently needed
			pVertex->tu = 0;
			pVertex->tv = 1 - y0;

			pVertex ++;

			//Add two indices except for last ring 
			if (ring != nRings) 
			{
				*pIndices = wVerticeIndex ; 
				pIndices++;
				*pIndices = wVerticeIndex + (WORD) (nSegments + 1) ; 
				pIndices++;
				wVerticeIndex++; 
			} ; 

		} ; // end for seg 
	} // end for ring 


	pIndexBuffer->Unlock();
	pVertexBuffer->Unlock();

	return true ; 
} 


bool CSky::generateSun()
{
	HRESULT hr;

	//Generate billboard buffer
	SSkyVertex vertices[] =
	{
		{ 0.500f, 0.0f, 0.500f, 1.0f, 0.0f, }, // x, y, z, tu, tv
		{ -0.500f, 0.0f, 0.500f, 0.0f, 0.0f, },
		{  0.500f, 0.0f, -0.500f, 1.0f, 1.0f, },
		{ -0.500f, 0.0f, -0.500f, 0.0f, 1.0f, },
	};

	if( FAILED( pD3DDevice->CreateVertexBuffer(4*sizeof(SSkyVertex),
		 0 , D3DFVF_SKYVERTEX, D3DPOOL_DEFAULT, &pSunVertexBuffer, NULL ) ) )
		return false;

	VOID* pVertices;
	if(FAILED(pSunVertexBuffer->Lock( 0, sizeof(vertices), (void**)&pVertices, 0)))
		return false;

	memcpy(pVertices, vertices, sizeof(vertices));

	pSunVertexBuffer->Unlock();

	//Generate
	V(pD3DDevice->CreateTexture(128, 128, 0, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &pSunTexture, 
	                            NULL));

	//Create the buffer with the data
	DWORD* topBuffer = new DWORD[128*128];
	int i = 0;
	int a, r, g, b;
	for(int x = 0; x < 128; x++)
	{
		for(int z = 0; z < 128; z++)
		{
			//Calculate distance from center (squared)
			int   dx   = abs(x-64);
			int   dz   = abs(z-64);
			float dist = dx*dx + dz*dz;

			//Scale color based on distance
			float intensity = (1.0f - (dist / /*8160*/2000));
			if(intensity < 0.0001f)
				intensity = 0.0f;
			intensity = intensity * 255;

			//Set all the components
			a = 255;
			r = intensity;
			b = intensity;
			g = intensity;
			topBuffer[i] = ((a<<24L)+(r<<16L)+(g<<8L)+(b));
			i++;
		}
	}

	//Copy the data into the texture
	D3DLOCKED_RECT rect;
	pSunTexture->LockRect(0, &rect, NULL, 0);

	memcpy(rect.pBits, topBuffer, sizeof(DWORD)*128*128);

	pSunTexture->UnlockRect(0);

	if(topBuffer)
		delete [] topBuffer;

	return true;
}
